//
//  GIFFrameStore.swift
//  GifUsage
//
//  Created by Ting Han on 2020/12/19.
//  Copyright © 2020 Ting Han. All rights reserved.
//

import UIKit
import ImageIO

struct GIFFrame {
    var image: UIImage?
    var duration: TimeInterval
}

class GIFFrameStore {
    var data: Data
    var imageSource: CGImageSource
    let size: CGSize
    let contentMode: UIView.ContentMode
    
    let loopCount: Int // 0 表示不做限制
    var currentLoop = 0
    var isFinished: Bool = false // 用于循环
    
    let frameCount: Int
    var gifFrames = [GIFFrame]()
    var bufferFrameCount: Int
    
    var currentIndex: Int = 0 {
        didSet {
            previousIndex = currentIndex
        }
    }
    var previousIndex: Int = 0 {
        didSet {
            preloadFrameQueue.async {
                self.updatePreloadedFrames()
            }
        }
    }
    // 循环一次需要时间
    var loopDuration: TimeInterval = 0
    let maxTimeStep = 1.0
    
    var shouldResizeFrames = true
    
    // 循环控制时间
    var timeSinceLastFrameChange: TimeInterval = 0.0
    
    init(data: Data, size: CGSize, contentMode: UIView.ContentMode, framePreloadCount: Int, loopCount: Int) {
        self.data = data
        let option = [String(kCGImageSourceShouldCache) : kCFBooleanFalse] as CFDictionary
        // 设置不对解码后的位图缓存
        imageSource = CGImageSourceCreateWithData(data as CFData, option) ?? CGImageSourceCreateIncremental(option)
        frameCount = CGImageSourceGetCount(imageSource)
        gifFrames.reserveCapacity(frameCount)
        self.size = size
        self.contentMode = contentMode
        self.bufferFrameCount = framePreloadCount
        self.loopCount = loopCount
    }
    
    func  prepareFrames(_ completionHandler: (() -> Void)? = nil) {
        preloadFrameQueue.async {
            self.setupFrames()
            completionHandler?()
        }
    }
    
    func shouldChangeFrame(with duration: CFTimeInterval, handler: (Bool) -> Void) {
        timeSinceLastFrameChange += min(maxTimeStep, duration)
        if currentDuration > timeSinceLastFrameChange {
            handler(false)
        } else {
            timeSinceLastFrameChange -= currentDuration
            incrementCurrentFrameIndex()
            handler(true)
        }
    }
    
    private lazy var preloadFrameQueue: DispatchQueue = {
        return DispatchQueue(label: "com.gif.preloadQueue")
    }()
}


extension GIFFrameStore {
    func setupFrames() {
        resetFrames()
        var totalDuration: TimeInterval = 0
        
        (0 ..< frameCount).forEach { index in
            let duration = CGImageSourceFrameDuration(imageSource: imageSource, atIndex: index)
            totalDuration += min(duration, maxTimeStep)
            gifFrames.append(GIFFrame(image: nil, duration: duration))
            
            if index > bufferFrameCount { return }
            
            gifFrames[index].image = loadFrame(at: index)
        }
    }
    
    func resetFrames() {
        gifFrames.removeAll()
    }
    
    func updatePreloadedFrames() {
        guard preloadingIsNeeded else { return }
        // 以当前index的next为起点，计算整个gifFrames应当放置的动图的frameIndex
        let indices = preloadIndices(withStartingIndex: currentIndex)
        indices.forEach { index in
            // 只需要将image = nil的那个设置为正确的image
            let currentFrame = gifFrames[index]
            guard currentFrame.image == nil else { return }
            gifFrames[index].image = loadFrame(at: index)
        }
    }
    
    func preloadIndices(withStartingIndex index: Int) -> [Int] {
        let nextIndex = increment(frameIndex: index)
        let lastIndex = increment(frameIndex: index, by: bufferFrameCount)
        
        if lastIndex >= nextIndex {
            return [Int](nextIndex...lastIndex)
        } else {
            return [Int](nextIndex..<frameCount) + [Int](0...lastIndex)
        }
    }
    
    func incrementCurrentFrameIndex() {
        currentIndex = increment(frameIndex: currentIndex)
        if isLastLoop(loopIndex: currentLoop) && isLastFrame(frameIndex: currentIndex) {
            isFinished = true
        } else if currentIndex == 0 {
            currentLoop = currentLoop + 1
        }
    }
    
    func loadFrame(at index: Int) -> UIImage? {
        guard let imageRef = CGImageSourceCreateImageAtIndex(imageSource, index, nil) else { return nil }
        let image = UIImage(cgImage: imageRef)
        let scaledImage: UIImage?
        
        if shouldResizeFrames {
            switch self.contentMode {
            case .scaleAspectFit: scaledImage = image.constrained(by: size)
            case .scaleAspectFill: scaledImage = image.filling(size: size)
            default: scaledImage = image.resized(to: size)
            }
        } else {
            scaledImage = image
        }
        
        return scaledImage
    }
    
}

extension GIFFrameStore {
    
    var isGIF: Bool {
        imageSource.isGIF
    }
    
    var currentImage: UIImage? {
        return frame(at: currentIndex)
    }
    
    var currentDuration: TimeInterval {
        return duration(at: currentIndex)
    }
    
    func frame(at index: Int) -> UIImage? {
        gifFrames[safe: index]?.image
    }
    
    func duration(at index: Int) -> TimeInterval {
        gifFrames[safe: index]?.duration ?? TimeInterval.infinity
    }
    
    // preload
    var preloadingIsNeeded: Bool {
        return bufferFrameCount < frameCount - 1
    }
    
    func increment(frameIndex: Int, by value: Int = 1) -> Int {
        (frameIndex + value) % frameCount
    }
    
    func isLastFrame(frameIndex: Int) -> Bool {
        frameIndex == frameCount - 1
    }
    
    func isLastLoop(loopIndex: Int) -> Bool {
        loopIndex == loopCount - 1
    }
}
